module jinji.post.search;

import jinji.common.query;
import jinji.post.model;
import jinji.tag.service;
import tame.fixed.stack;

/// 转义搜索文本
private void escape(S)(in string expr, ref S sink) @trusted if (isStringSink!S) {
	size_t start;

	bool quote;
	foreach (i, c; expr) {
		string escaped = void;

		// dfmt off
		switch (c) {
		case ' ':
			escaped = quote ? `" ` : ` `;
			quote = false;
			break;
		case '|':
			escaped = quote ? `" OR ` : ` OR `;
			quote = false;
			break;
		case '-': escaped = quote ? `-` : `NOT `; break;
		case '(': escaped = `(`; break;
		case ')': escaped = `)`; break;
		case '*':
			escaped = quote ? `"* ` : `* `;
			quote = false;
			break;
		case '"': escaped = `""`; break;
		default:
			checkArg(expr.stringof, c >= ' ', "无效的搜索表达式");

			if (!quote) {
				quote = true;
				sink ~= '"';
			}
		continue;
		}
		// dfmt on
		sink ~= resolve(expr[start .. i]);
		sink ~= escaped;
		start = i + 1;
	}

	sink ~= expr[start .. $];
	if (quote)
		sink ~= '"';
}

///
unittest {
	import std.array : appender;

	static void test(string expr, string expected) {
		auto r = appender!string;
		escape(expr, r);
		assert(r[] == expected, r[]);
	}

	test("a", `"a"`);
	test("a b", `"a" "b"`);
	test("a|b", `"a" OR "b"`);
	test("a -b", `"a" NOT "b"`);
	test("a(b)", `"a(b)"`);
	test("a_(b)", `"a_(b)"`);
	test("a (b|c )", `"a" ("b" OR "c" )`);
	test("a -(b|c )", `"a" NOT ("b" OR "c" )`);
	test("(a|b )-c", `("a" OR "b" )NOT "c"`);
	test("a|(b -c )", `"a" OR ("b" NOT "c" )`);
	test("a\"b", `"a""b"`);
	test("a*b", `"a"* "b"`);
	test("*-a b|a -b", `* NOT "a" "b" OR "a" NOT "b"`);
}

extern (C):

/// 搜索
@"帖子id列表"@QR!"number"@Action void search(ref Request req,
	@"搜索表达式" string expr = "",
	@"筛选条件"@type("Filter[]") Filter[] filters = [],
	@"排序方式" string order = "created_at desc",
	@"偏移量"int offset = 0,
	@"最大结果数"int limit = 20) {
	if (!order.length)
		order = "created_at desc";
	limit = min(limit, 50);
	if (!expr.length) {
		req.send(query!(count!Post).get!long);
		foreach (row; query(SB.select!(Post.id).orderByCE(order)
				.limit("$1").offset("$2"), limit, offset))
			req.send(row.get!long);
		return;
	}

	auto buf = fixedBuf!(4096, "搜索文本过长");
	escape(expr, buf);
	if (filters.length) {
		return;
	}
	long count = query!(SB.select!"count(*)"
			.from("tagidx")
			.where("tagidx MATCH $1"))(buf.data).get!long;
	req.send(count);
	if (!count || !limit)
		return;
	enum sql = SB.select!(Post.id)
			.where("id in (SELECT rowid FROM tagidx WHERE tagidx MATCH $1)");
	foreach (row; query(sql.orderByCE(order).limit("$2").offset("$3"), buf.data, limit, offset))
		req.send(row.get!long);
}

private:
///
struct Parser {
	string s;
	size_t frontLen;

	@property bool empty() const => s.length == 0;

	@property auto front() @trusted {
		import core.stdc.string : memchr;

		if (!frontLen && s.length) {
			int c = void;
			if (s[0] == '"') {
				s = s[1 .. $];
				c = '"';
			} else {
				c = ' ';
			}
			const p = memchr(s.ptr, c, s.length);
			frontLen = p ? p - cast(void*)s.ptr : s.length;
		}
		return s[0 .. frontLen];
	}

	void popFront() {
		s = s[frontLen + (frontLen < s.length) .. $];
		frontLen = 0;
	}
}

unittest {
	{
		auto s = Parser("foo");
		assert(!s.empty);
		assert(s.front == "foo");
		s.popFront;
		assert(s.empty);
	}
	auto s = Parser(`""`);
	assert(!s.empty);
	assert(s.front == "", s.front);
}
